//
// request_parser.cpp
// ~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <stdlib.h>
#include "request_parser.hpp"
#include "request.hpp"

namespace http {
namespace server {

request_parser::request_parser()
  : state_(method_start)
{
}

void request_parser::reset()
{
  state_ = method_start;
}

//boost::tribool request_parser::consume(request& req, char input)
TriBool request_parser::consume(request& req, char input)
{
  switch (state_)
  {
  case method_start:
    if (!is_char(input) || is_ctl(input) || is_tspecial(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      state_ = method;
      req.method.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case method:
    if (input == ' ')
    {
      state_ = uri;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      req.method.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case uri_start:
    if (is_ctl(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      state_ = uri;
      req.uri.push_back(input);
      req.pureurl.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case uri:
    if (input == ' ')
    {
      state_ = http_version_h;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (is_ctl(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
        // add by lbw for parse params 0917
        if ('?' == input)
        {
            state_ = url_param_start;
        }
        else
        {
            req.pureurl.push_back(input);
        }

      req.uri.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }

    //////////////////////////////////////////////////////////////////////////
    // add by lbw for parse params 0917 begin
    case url_param_start:
    {
        if (input == ' ')
        {
            state_ = http_version_h;            
            return tri_Indeterminate;
        }
        else if (is_ctl(input))
        {            
            return tri_False;
        }
        else
        {
            req.params.push_back(request::Param());
            req.params.back().name.push_back(input);
            req.uri.push_back(input);   // it's also part of url

            state_ = url_param_name;        
            return tri_Indeterminate;
        }
    }
    case url_param_name:
    {
        if (input == ' ')
        {
            state_ = http_version_h;            
            return tri_Indeterminate;
        }
        else if (is_ctl(input))
        {            
            return tri_False;
        }
        else if (input == '=')
        {            
            state_ = url_param_value;
            req.uri.push_back(input);   // it's also part of url
            return tri_Indeterminate;
        }
        else
        {            
            req.params.back().name.push_back(input);
            req.uri.push_back(input);   // it's also part of url
            return tri_Indeterminate;
        }
    }
    case url_param_value:
    {
        if (input == ' ')
        {
            state_ = http_version_h;            
            return tri_Indeterminate;
        }
        else if (is_ctl(input))
        {            
            return tri_False;
        }
        else if (input == '&')
        {   
            req.uri.push_back(input);   // it's also part of url

            state_ = url_param_start;
            return tri_Indeterminate;
        }
        else
        {            
            req.params.back().value.push_back(input);
            req.uri.push_back(input);   // it's also part of url
            return tri_Indeterminate;
        }
    }
    // add by lbw for parse params 0917 end
    //////////////////////////////////////////////////////////////////////////

  case http_version_h:
    if (input == 'H')
    {
      state_ = http_version_t_1;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_t_1:
    if (input == 'T')
    {
      state_ = http_version_t_2;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_t_2:
    if (input == 'T')
    {
      state_ = http_version_p;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_p:
    if (input == 'P')
    {
      state_ = http_version_slash;
      //return boost::indeterminate;
      return tri_Indeterminate;      
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_slash:
    if (input == '/')
    {
      req.http_version_major = 0;
      req.http_version_minor = 0;
      state_ = http_version_major_start;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_major_start:
    if (is_digit(input))
    {
      req.http_version_major = req.http_version_major * 10 + input - '0';
      state_ = http_version_major;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_major:
    if (input == '.')
    {
      state_ = http_version_minor_start;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (is_digit(input))
    {
      req.http_version_major = req.http_version_major * 10 + input - '0';
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_minor_start:
    if (is_digit(input))
    {
      req.http_version_minor = req.http_version_minor * 10 + input - '0';
      state_ = http_version_minor;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case http_version_minor:
    if (input == '\r')
    {
      state_ = expecting_newline_1;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (is_digit(input))
    {
      req.http_version_minor = req.http_version_minor * 10 + input - '0';
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case expecting_newline_1:
    if (input == '\n')
    {
      state_ = header_line_start;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case header_line_start:
    if (input == '\r')
    {
      state_ = expecting_newline_3;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (!req.headers.empty() && (input == ' ' || input == '\t'))
    {
      state_ = header_lws;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      req.headers.push_back(header());
      req.headers.back().name.push_back(input);
      state_ = header_name;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case header_lws:
    if (input == '\r')
    {
      state_ = expecting_newline_2;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (input == ' ' || input == '\t')
    {
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (is_ctl(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      state_ = header_value;
      req.headers.back().value.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case header_name:
    if (input == ':')
    {
      state_ = space_before_header_value;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      req.headers.back().name.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case space_before_header_value:
    if (input == ' ')
    {
      state_ = header_value;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case header_value:
    if (input == '\r')
    {
      state_ = expecting_newline_2;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else if (is_ctl(input))
    {
      //return false;
      return tri_False;
    }
    else
    {
      req.headers.back().value.push_back(input);
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
  case expecting_newline_2:
    if (input == '\n')
    {
      state_ = header_line_start;
      //return boost::indeterminate;
      return tri_Indeterminate;
    }
    else
    {
      //return false;
      return tri_False;
    }
  case expecting_newline_3:
	  if (input == '\n')
	  {
		  if( req.method != "POST" ) 
		  {
			  // finished
			  //return true;
              return tri_True;
		  } else
		  {
			  // this is a post request, so we need to read the content
			  req.content_length = 0;
			  for( std::vector<header>::iterator ph = req.headers.begin();
				  ph != req.headers.end(); ph++ )
			  {
				  if( (*ph).name == "Content-Length" ) {
					  req.content_length = atoi( (*ph).value.c_str());
					  break;
				  }
			  }

			  state_ = reading_content;
			  //return boost::indeterminate;
              return tri_Indeterminate;
		  }
	  } else
	  {
		  //return false;
          return tri_False;
	  }
  case reading_content:
	  if (input!='\0')
	  {
		  req.content.push_back(input);
	  }
	  //req.content.push_back(input);	// the content contains an '0x0d' and an '0x0a', we must wipe it off in logic
	  if( req.content.length() == req.content_length )	  
	  {
		  //return true;
          return tri_True;
	  } 
	  else
	  {
		  //return boost::indeterminate;
          return tri_Indeterminate;
	  }

  default:
    //return false;
    return tri_False;
  }
}

bool request_parser::is_char(int c)
{
  return c >= 0 && c <= 127;
}

bool request_parser::is_ctl(int c)
{
  return c >= 0 && c <= 31 || c == 127;
}

bool request_parser::is_tspecial(int c)
{
  switch (c)
  {
  case '(': case ')': case '<': case '>': case '@':
  case ',': case ';': case ':': case '\\': case '"':
  case '/': case '[': case ']': case '?': case '=':
  case '{': case '}': case ' ': case '\t':
    return true;
  default:
    return false;
  }
}

bool request_parser::is_digit(int c)
{
  return c >= '0' && c <= '9';
}



} // namespace server
} // namespace http
